#include "BluetoothRadio.h"

using namespace std;

#include "SocketException.h"
#include "BluetoothConnection.h"
#include "Util.h"
#include "NetworkEventReceiver.h"
#include "BufferedReader.h"

//
//
//
BTH_ADDR BluetoothRadio::getMacAddress() {
	return mRadioInfo.address.ullLong;
}	


tstring BluetoothRadio::getName() {
	return mRadioInfo.szName;
}


BOOL __stdcall callback(
  ULONG uAttribId,
  LPBYTE pValueStream,
  ULONG cbStreamSize,
  LPVOID pvParam)
{
   printf("Callback %d\n", uAttribId);

	SDP_ELEMENT_DATA element;

	if (ERROR_SUCCESS != BluetoothSdpGetElementData(pValueStream,  cbStreamSize, &element)) {
		throw Exception("BluetoothSdpGetElementData()");
	}


	return true;
}



void BluetoothRadio::setCallback( IBluetoothConnectionEventReceiver* callback ) {
	mCallback = callback;
}



//
//
//
vector<BluetoothDevice> BluetoothRadio::queryDevices() {
	vector<BluetoothDevice> result;


	BLUETOOTH_DEVICE_SEARCH_PARAMS searchParams = {0};
	searchParams.dwSize = sizeof(BLUETOOTH_DEVICE_SEARCH_PARAMS);
	searchParams.fReturnAuthenticated = TRUE;
	searchParams.hRadio = mRadioHandle;
	searchParams.fReturnConnected = TRUE;
	searchParams.fReturnRemembered = TRUE;
	searchParams.fReturnUnknown = TRUE;
	searchParams.fIssueInquiry = TRUE;
	searchParams.cTimeoutMultiplier = 2; // multiples of 1.28 seconds
	
	BLUETOOTH_DEVICE_INFO deviceInfo = {0};
	deviceInfo.dwSize = sizeof(BLUETOOTH_DEVICE_INFO);

	printf("starting Bluetooth device discovery\n");
		
	HBLUETOOTH_DEVICE_FIND hFind = ::BluetoothFindFirstDevice( &searchParams, &deviceInfo);
	if (hFind != NULL) { 
		do {
			BluetoothDevice device(deviceInfo, mRadioHandle);
			printf("found device: %.12llX\n", deviceInfo.Address.ullLong);
			
			device.updateServices();

			result.push_back(device);	
		} while (::BluetoothFindNextDevice(hFind, &deviceInfo));
	
		// find out why the query ended
		switch (GetLastError()) {
			case ERROR_NO_MORE_ITEMS: printf("no more devices found\n");break; 
			default: throw SocketException(_T("BluetoothFindNextDevice"), GetLastError());
		}

		if (::BluetoothFindDeviceClose(hFind) == FALSE) 
			throw SocketException(_T("BluetoothFindDeviceClose"), GetLastError());
	} else {
		printf("no devices found\n");
	}
	
	return result;
}


//
// Throws SocketException
//
BluetoothConnection BluetoothRadio::connectTo(BluetoothDevice device) {

	// create the Socket 
	if ((mSocket = ::socket( AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM))
		== INVALID_SOCKET) throw SocketException(_T("BluetoothRadio::connectTo(): socket: "),  WSAGetLastError());

	BLUETOOTH_DEVICE_INFO deviceInfo = device.getDeviceInfo();

	BLUETOOTH_OOB_DATA_INFO oobDataInfo;
	DWORD result = ::BluetoothAuthenticateDeviceEx(NULL, mRadioHandle.getHandle(), &deviceInfo, &oobDataInfo, MITMProtectionNotRequired);
	switch (result) {
		case ERROR_SUCCESS: break;
		case ERROR_NO_MORE_ITEMS: break;
		default: throw SocketException(_T("BluetoothRadio::connectTo(): BluetoothAuthenticateDevice: "), result);
	};

	SOCKADDR_BTH remoteAddress;
	remoteAddress.addressFamily = AF_BTH;
	remoteAddress.btAddr = device.getMacAddress();
	remoteAddress.port = BT_PORT_ANY;
	remoteAddress.serviceClassId = MYGUID ;//RFCOMM_PROTOCOL_UUID;
	
	if (::connect( mSocket, (sockaddr*) &remoteAddress, sizeof(SOCKADDR_BTH))
		== INVALID_SOCKET) throw SocketException(_T("BluetoothRadio::connectTo(): connect: "),  WSAGetLastError());

	return BluetoothConnection(remoteAddress, mSocket);
}
	



//
// Start listening for incoming BluetoothConnections
//
// Throws SocketException
void BluetoothRadio::run()  {

	// make sure this device is discoverable
	if (!BluetoothIsDiscoverable(mRadioHandle))
		if (!BluetoothEnableDiscovery(mRadioHandle, TRUE))
			throw Exception("could not make this service discoverable\n");
			
	// Struct containing Information about the local BT device
	SOCKADDR_BTH localAddress = {0}; 
	localAddress.addressFamily = AF_BTH;
	localAddress.port = BT_PORT_ANY;

	// create the Socket (Bluetooth socket using RFCOMM-Protocol)
	if ((mSocket = ::socket( AF_BTH, SOCK_STREAM, BTHPROTO_RFCOMM))
		== INVALID_SOCKET) throw SocketException(_T("BluetoothRadio::run(): socket: "),  WSAGetLastError());
	
	// bind socket to local address (tell the socket which radio to use)
	if (::bind(mSocket, (sockaddr*)&localAddress, sizeof(SOCKADDR_BTH))
		== SOCKET_ERROR) throw SocketException(_T("BluetoothRadio::run(): bind: "),  WSAGetLastError());

	// get Information about the selected device and store it in 'localAddress'
	int siize = sizeof(SOCKADDR_BTH);
	if (::getsockname(mSocket,(sockaddr *)&localAddress, &siize)
		== SOCKET_ERROR) throw SocketException(_T("BluetoothRadio::run(): getsockname: "),  WSAGetLastError());
	
			
	// To be able to receive BluetoothConnections, we need to announce our service via SDP (Service Discovery Protocol)
	CSADDR_INFO CSAddrInfo = {0};
    CSAddrInfo.LocalAddr.iSockaddrLength = sizeof( SOCKADDR_BTH );
    CSAddrInfo.LocalAddr.lpSockaddr = (LPSOCKADDR)&localAddress;
    CSAddrInfo.RemoteAddr.iSockaddrLength = sizeof( SOCKADDR_BTH );
    CSAddrInfo.RemoteAddr.lpSockaddr = (LPSOCKADDR)&localAddress;
    CSAddrInfo.iSocketType = SOCK_STREAM;
    CSAddrInfo.iProtocol = BTHPROTO_RFCOMM;

	// Information about the announcement
    WSAQUERYSET wsaQuerySet = {0};	
    wsaQuerySet.dwSize = sizeof(WSAQUERYSET);
    wsaQuerySet.lpServiceClassId = (LPGUID) &MYGUID; // the GUID identifiying the service
    wsaQuerySet.lpszServiceInstanceName = _T("RVM1 Service"); // "Sample Bluetooth Server"
    wsaQuerySet.lpszComment = _T("Example Service instance registered in the directory service through RnR");
    wsaQuerySet.dwNameSpace = NS_BTH;
    wsaQuerySet.dwNumberOfCsAddrs = 1;      // Must be 1.
    wsaQuerySet.lpcsaBuffer = &CSAddrInfo; // Req'd.

	// start anouncement?
    if ( ::WSASetService(&wsaQuerySet, RNRSERVICE_REGISTER , 0)  
		== SOCKET_ERROR) throw SocketException(_T("BluetoothRadio::run(): WSASetService() call failed."),  WSAGetLastError());
	
	// listen on the Socket
	if (::listen(mSocket, SOMAXCONN) 
		== SOCKET_ERROR) throw SocketException(_T("BluetoothRadio::run(): listen: "),  WSAGetLastError());
	

	SOCKADDR_BTH address = {0};	
	SOCKET client;
	int addrlen = sizeof(SOCKADDR_BTH);

	// the receiver will listen for network events 
	// and forward them to registered handlers
	NetworkEventReceiver mReceiver;
	mReceiver.start();

	vector<BluetoothConnection> mBluetoothConnections;
	// start accepting incoming BluetoothConnections
	while (true) {
		if ((client = ::accept( mSocket, (sockaddr*) &address, &addrlen))
			== INVALID_SOCKET) throw SocketException(_T("BluetoothRadio::run(): accept: "),  WSAGetLastError());

		printf( "accepted BluetoothConnection from %.12llX\n", address.btAddr);

		
		mReceiver.registerOnClose(client, 
			[](SOCKET closedSocket)
			{ 
				printf("onClose called for socket: %u\n",closedSocket);
			});
			

		if (mCallback) mCallback->onConnect(BluetoothConnection(address,client));

		mBluetoothConnections.push_back(BluetoothConnection(address,client));	
	}
}


//
//
//
BluetoothRadio::~BluetoothRadio() {
	// release the Socket
	if (mSocket != INVALID_SOCKET) ::closesocket(mSocket);
}


//
// creates a list of the Bluetooth Devices (Dongles) on the local machine
//
// throws SocketException
vector<BluetoothRadio> BluetoothRadio::queryLocalBtRadios() {

	BLUETOOTH_FIND_RADIO_PARAMS params = {0};
	params.dwSize = sizeof(BLUETOOTH_FIND_RADIO_PARAMS);
		
	// the search handle
	HBLUETOOTH_RADIO_FIND brfHandle = {0};

	// use shared_ptr to make sure Handles are closed when all references are gone
	vector<BluetoothRadio> localBtRadios;;

	HANDLE hRadio;
	// query the first local Bluetooth radio
	brfHandle = ::BluetoothFindFirstRadio( &params, &hRadio);
	if (brfHandle == NULL) throw SocketException(_T("BluetoothRadio::queryLocalBtRadios(): BluetoothFindFirstRadio"), GetLastError());
	else do {

		// retrieve information about the Radio
		BLUETOOTH_RADIO_INFO info = {0};
		info.dwSize = sizeof(BLUETOOTH_RADIO_INFO);

		DWORD result = ::BluetoothGetRadioInfo(hRadio, &info);
		if (result != ERROR_SUCCESS) throw SocketException( _T("BluetoothRadio::queryLocalBtRadios(): BluetoothGetRadioInfo"), result);

		// add it to the list of Radios
		localBtRadios.push_back( BluetoothRadio(Handle(hRadio), info));

	} while (::BluetoothFindNextRadio( brfHandle, &hRadio));

	// find out why the query ended
	switch (GetLastError()) {
	case ERROR_NO_MORE_ITEMS: break; // everything went as expected
	default: throw SocketException(_T("BluetoothRadio::queryLocalBtRadios(): BluetoothFindNextRadio"), GetLastError());
	}

	// close the search handle
	if (::BluetoothFindRadioClose(brfHandle)
		== false) throw SocketException(_T("BluetoothRadio::queryLocalBtRadios(): BluetoothFindRadioClose"), GetLastError());

	return localBtRadios;
}


//
//
//
// BtRadio objects can only be retrieved via queryLocalBtRadios
BluetoothRadio::BluetoothRadio(Handle handle, BLUETOOTH_RADIO_INFO info) 
: mRadioHandle(handle) 
, mRadioInfo(info)
, mSocket(INVALID_SOCKET)
{}	


